package ar.uba.fi.tonyvaliente.documents;

import java.util.NoSuchElementException;

/**
 * Representa un filtro que aplica el operador AND sobre los
 * elementos recuperados por otros dos filtros cualesquiera
 * Esta implementado de modo tal que no necesite generar
 * una nueva coleccion con los elementos comunes de ambos iteradores
 * @author dmorello
 */
public class DocumentANDFilter extends DocumentBinaryOperatorFilter {
	
	public DocumentANDFilter(DocumentFilter filter1,
			DocumentFilter filter2, boolean negated) {
		
		super(filter1, filter2, negated);
	}

	
	/**
	 * Crea un ResultIterator capaz de aplicar el operador AND
	 * sobre los resultados devueltos por los ResultIterators
	 * correspondientes a los filtros asociados a esta instancia
	 */
	@Override
	protected ResultIterator createResultIterator(int totalDocumentos) {
		ResultIterator it1 = filter1.getResultIterator();
		ResultIterator it2 = filter2.getResultIterator();
		return new DocumentANDResultIterator(it1, it2, totalDocumentos);
	}
	
	/**
	 * ResultIterator capaz de aplicar el operador AND
	 * sobre los resultados devueltos por los ResultIterators
	 * correspondientes a los filtros asociados a esta instancia
	 */
	private class DocumentANDResultIterator extends ResultIterator {
		
		private ResultIterator it1;
		private ResultIterator it2;
		
		// Valor de hasNext que se calcula solo una vez
		// ante varias llamadas a hasNext()
		private Boolean hasNextValue;
		
		private int id1;
		private int id2;
		private int nextId;
		
		/**
		 * Crea una instancia de la clase que aplica el operador AND
		 * sobre los elementos devueltos por otros dos iteradores
		 * @param it1 un ResultIterator cualquiera
		 * @param it2 un ResultIterator cualquiera
		 * @param totalDocumentos total de documentos del indice invertido
		 */
		public DocumentANDResultIterator(ResultIterator it1,
					ResultIterator it2, int totalDocumentos) {
			super(totalDocumentos);
			this.it1 = it1;
			this.it2 = it2;
			this.hasNextValue = null;
			
			// Seteo estos valores para poder avanzar cada uno
			// de los iteradores cuando sea necesario
			id1 = -1;
			id2 = 0;
			nextId = -1;
		}
		
		public boolean hasNext() {
			
			// Si no verifique que haya un proximo, lo hago ahora
			// y ya dejo seteado ese valor para no calcularlo innecesariamente
			if (hasNextValue == null) {
				hasNextValue =  getHasNextValue();
			}
			
			return hasNextValue.booleanValue();
		}
		
		public Integer next() {
			if (hasNext()) {
				// La llamada a hasNext modifico el valor de nextId
				return nextId;
			} else {
				throw new NoSuchElementException("No hay mas elementos "
						+ "en el iterador");
			}
			/*
			// Si nextId no es -1 hubo una llamada a hasNext -> devuelvo
			if (nextId != -1) {
				return nextId;
			} else {
				// Si es -1, o no se llamo a hasNext o no hay mas elementos
				if (this.hasNext()) {
					// La llamada a hasNext modifico el valor de nextId
					return nextId;
				} else {
					throw new NoSuchElementException("No hay mas elementos "
							+ "en el iterador");
				}
			}*/
		}
		
		/**
		 * Verifica si hay mas elementos comunes entre ambos iteradores
		 * avanzando al siguiente elemento de alguno de ellos si corresponde
		 * y devuelve el valor logico que indica si hay mas elementos o no.
		 * En caso de haber mas elementos, deja el valor del siguiente
		 * seteado en el atributo nextId.
		 * @return Boolean.TRUE si hay mas elementos,
		 * Boolean.FALSE si no hay mas
		 */
		private Boolean getHasNextValue() {
			boolean loop = true;
			nextId = -1;
			
			// Voy a ciclar mientras ambos iteradores tengan elementos
			// Si uno llega a su fin, no hay mas elementos para hacer un AND
			while (it1.hasNext() && it2.hasNext() && loop) {
				nextId = -1; // Ver si sirve esto
				
				// Avanzo el/los iterador/es que corresponda
				if (id1 < id2) id1 = it1.next();
				if (id2 < id1) id2 = it2.next();
				if (id1 == id2) {
					nextId = id2; // Queda seteado el next
					id1--;        // Permite el avance posterior de iteradores
					loop = false; // Encontre el next -> salgo del ciclo
				}
			}
			
			return Boolean.valueOf(nextId == -1); 
		}
	}

}
